/*
 * xsskillService.js
 * Main file for XSSKill plugin.
 * 
 * Description: Contains the main logic for checking for XSS vulnerabilities within a page.
 *              Vulnerabilities are checked on page load and page request.
 *              Uses regular expressions to check for possible unsafe requests/pages.
 * 
 *  Created By: Josh Barron, Justin Kenworthy, and Michael Siegrist
 * 
 *       Notes: Many aspects of this project were modeled after the NoScript plugin
 *                   Copyright (C) 2004-2008 Giorgio Maone - g.maone@informaction.com
 *              Regular expressions can be found in file regural_expressions.js
 * 
 *    Liscence: This program is free software; you can redistribute it and/or modify
 *              it under the terms of the GNU General Public License as published by
 *              the Free Software Foundation; either version 2 of the License, or
 *              (at your option) any later version.
 * 
 *              This program is distributed in the hope that it will be useful,
 *              but WITHOUT ANY WARRANTY; without even the implied warranty of
 *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *              GNU General Public License for more details.
 * 
 *              You should have received a copy of the GNU General Public License
 *              along with this program; if not, write to the Free Software
 *              Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */



// Useful constants and macros.
CI = Components.interfaces;
CC = Components.classes;
STATE_START = CI.nsIWebProgressListener.STATE_START;
STATE_STOP= CI.nsIWebProgressListener.STATE_STOP;
STATE_DOC = CI.nsIWebProgressListener.STATE_IS_DOCUMENT;

CID = Components.ID("{25444c60-c400-11dd-ad8b-0800200c9a66}");
NAME = "XSSKill XSS Protection Extension";
PROG_ID = "@joshua.barron/xsskill;1";

APP_STARTUP = "app-startup";
XPCOM_SHUTDOWN = "xpcom-shutdown";
HTTP_OMR = "http-on-modify-request";
HTTP_OER = "http-on-examine-request";

DEBUG = false;

// Used to listen to HTTP request traffic.
function XSSKillListener()
{
    this.wrappedJSObject = this;
    this.register();
}

XSSKillListener.prototype = {
	
	/****************************
	 * XSSKillListener Methods *
	 ****************************/
	
	// Register the listener for the desired events.
	register: function()
	{
        var observer = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
        observer.addObserver(this, HTTP_OMR, false);
        observer.addObserver(this, HTTP_OER, false);

	var w = Components.classes["@mozilla.org/appshell/window-mediator;1"]
			.getService(CI.nsIWindowMediator);

	var gBrowser = w.getMostRecentWindow("navigator:browser").getBrowser();
		
	gBrowser.addProgressListener(this, CI.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
	},
	
	// Unregister when done.
	unregister: function()
	{
		var observer = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
		try
		{
            observer.removeObserver(this, HTTP_OMR);
            observer.removeObserver(this, HTTP_OER);

			}
		catch(e)
		{
			this.writeConsole(e + " while unregistering");
		}
	},
	
	// Write a formatted message to the console.
	writeConsole: function(message)
	{
		message = "[XSSKill]: Event Listener: " + message + "\n";
		dump(message);
	},
	
  // nsIObserver functions:
	observe: function(subject, topic, data)
	{
		// Check for the kind of request we were sent.
		
		switch(topic)
		{
		  case HTTP_OMR:
		        break;
		  case HTTP_OER:

		      break;
		}
	},

	onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)   {
		// If you use myListener for more than one tab/window, use
		// aWebProgress.DOMWindow to obtain the tab/window which triggers the state change
		if(aFlag & STATE_START)    {
			// This fires when the load event is initiated
		}
		if(aFlag & STATE_STOP)    {

			var urlParamList = yoinkURLParameters(aRequest.name);
			// This fires when the load finishes
			var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
			              .getService(Components.interfaces.nsIPromptService);

			var w = Components.classes["@mozilla.org/appshell/window-mediator;1"]
					.getService(CI.nsIWindowMediator);


			for (var i = 0;i < urlParamList.length;i++) {
				if (urlParamList[i].match(/script[\w|\W]*?\/script/gi)) {
					prompts.alert(null, "ALERT!!", "Malicious script found in URL:" + urlParamList[i]);
				}
				if (urlParamList[i].match(/onMouseOver\W*=.*/gi)) {
					prompts.alert(null, "ALERT!!", "Malicious onMouseOver found in URL:" +  urlParamList[i]);
					}

			}
							
		}   
		return 0;
	},
	
	onLocationChange: function(aProgress, aRequest, aURI)   {
		// This fires when the location bar changes; i.e load event is confirmed
		// or when the user switches tabs. If you use myListener for more than one tab/window,
		// use aProgress.DOMWindow to obtain the tab/window which triggered the change.
		return 0;
	},
	
	// For definitions of the remaining functions see XULPlanet.com 
	onProgressChange: function() {return 0;},
	onStatusChange: function() {return 0;},
	onSecurityChange: function() {return 0;},
	onLinkIconAvailable: function() {return 0;}, 
	
	// nsISupports functions:
	QueryInterface: function(iid)
	{
		if(iid.equals(CI.nsIObserver) ||
		iid.equals(CI.nsIWebProgressListener) ||
		   iid.equals(CI.nsISupports)) return this;
		throw Components.results.NS_ERROR_NO_INTERFACE;
		return null;
	}
};


// Factory for XSSKillListener.
var XSSKillListenerFactory = {
	
    /**********************************
     * XSSKillListenerFactory Methods *
     **********************************/
  
    // Returns this object if for all interfaces supported.
    QueryInterface: function(iid)
    {
        if(iid.equals(CI.nsISupports) ||
           iid.equals(CI.nsIFactory)) return this;
        throw Components.results.NS_ERROR_NO_INTERFACE;
        return null;
    },

    // Creates a new instance of the XSSKillListener.
    createInstance: function(outer, iid)
    {
    	if(outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION;
        return (new XSSKillListener()).QueryInterface(iid);
    }
};


// Main component of XSSKill plugin.
var XSSKillService = {
	
	/*****************************
	 * XSSKillService Attributes *
	 *****************************/
	
	firstTime: true,                   // True if the component hasn't been registered yet.
	myCID: CID,                        // Component ID.
	myName: NAME,                      // Component name.
	myProgID: PROG_ID,                 // Component progress ID.
	listener: null,                    // Listener for HTTP events.
	myFactory: XSSKillListenerFactory, // Factory for this component.
	
	
	/**************************
	 * XSSKillService Methods *
	 **************************/
	
	// Component initialization.
	registerSelf: function(compMgr, fileSpec, location, type)
	{
        if(this.firstTime)
		{
			this.firstTime = false;
			compMgr = compMgr.QueryInterface(CI.nsIComponentRegistrar);
			compMgr.registerFactoryLocation(this.myCID, this.myName, this.myProgID, fileSpec, location, type);
			//var catMgr = CC['@mozilla.org/categorymanager;1'].getService(CI.nsICategoryManager);
			//catMgr.addCategoryEntry(APP_STARTUP, this.myName, this.myProgID, true, true);
		}
	},
	
	// Component destructor.
	unregisterSelf: function(compMgr, fileSpec, location)
	{
		compMgr = compMgr.QueryInterface(CI.nsIComponentRegistrar);
        compMgr.unregisterFactoryLocation(this.myCID, fileSpec);
        //var catMgr = CC['@mozilla.org/categorymanager;1'].getService(CI.nsICategoryManager);
        //catMgr.deleteCategoryEntry(APP_STARTUP, this.myProgID, true);
	},
	
	// Used to get the factory for the listener.
	getClassObject: function(cmpMgr, cid, iid)
	{
		if(!iid.equals(CI.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
		if(cid.equals(this.myCID)) return this.myFactory;
		
		throw Components.results.NS_ERROR_NO_INTERFACE;
	},
	
	// This component can unload.
	canUnload: function(compMgr)
	{
		return true;
	}
};


// Extension initialization.
function NSGetModule(compMgr, fileSpec)
{
	return XSSKillService;
}

// Yoink URL parameters
function yoinkURLParameters(urlStr) {

	var regexp = /(?:=([^&]*))/gi;

	var m = urlStr.match(regexp);
	
	if (m) {
		for (i = 0; i < m.length; i++) {
			m[i] = m[i].replace(/=/, "");
		}
	}

	return m;
}


// Class containing regular expressions for dangerous reflection, etc.
var XSSKillRegex = {
	
	/************************
	 * XSSKillRegex Methods *
	 ************************/
	
    // Search for <script> tags.
	checkScript: function(str)
	{
		
	}
}
